#ifndef ROUTEGUIDE_H
#define ROUTEGUIDE_H
#include <QDebug>
#include <QThread>
#include <chrono>
#include <condition_variable>
#include <iostream>
#include <memory>
#include <mutex>
#include <random>
#include <string>
#include <thread>


#include "helper.h"

#include <grpc/grpc.h>
#include <grpcpp/alarm.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#ifdef BAZEL_BUILD
#include "examples/protos/route_guide.grpc.pb.h"
#else
#include "route_guide.grpc.pb.h"
#endif

using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientReader;
using grpc::ClientReaderWriter;
using grpc::ClientWriter;
using grpc::Status;
using routeguide::Feature;
using routeguide::Point;
using routeguide::Rectangle;
using routeguide::RouteGuide;
using routeguide::RouteNote;
using routeguide::RouteSummary;

Point MakePoint(long latitude, long longitude) ;

Feature MakeFeature(const std::string& name,
                    long latitude, long longitude);

RouteNote MakeRouteNote(const std::string& message,
                        long latitude, long longitude);

class RouteGuideClient {
public:
    RouteGuideClient(std::shared_ptr<Channel> channel, const std::string& db)
        : channel_(channel),
        stub_(RouteGuide::NewStub(channel))
    {
        routeguide::ParseDb(db, &feature_list_);


    }

    void GetFeature() ;

    bool GetOneAsyncFeature();

    void ListFeatures();

    void AsyncListFeatures();

    void RecordRoute() ;

    void AsyncRecordRoute();

    void RouteChat() ;

    void AsyncRouteChat() ;

    void GetAsyncFeatureRet(Status);

private:

    bool GetOneFeature(const Point& point, Feature* feature) ;

    std::shared_ptr<Channel> channel_;
    const float kCoordFactor_ = 10000000.0;
    std::unique_ptr<RouteGuide::Stub> stub_;
    std::vector<Feature> feature_list_;

    std::function<void(Status)> m_func;
    Feature replay;
};


class Reader : public grpc::ClientReadReactor<Feature> {
public:
    Reader(RouteGuide::Stub* stub, float coord_factor,
           routeguide::Rectangle& rect)
        : coord_factor_(coord_factor) {
        stub->experimental_async()->ListFeatures(&context_, &rect, this);
        StartRead(&feature_);
        StartCall();
    }
    void OnReadDone(bool ok) override {
        if (ok) {
            qDebug() << "OnReadDone" << QThread::currentThreadId();

            qDebug() << "Found feature called " << feature_.name().c_str() << " at "
                      << feature_.location().latitude() / coord_factor_ << ", "
                      << feature_.location().longitude() / coord_factor_;

             qDebug() << "OnReadDone" << QThread::currentThreadId();
            StartRead(&feature_);
        }
        else
        {
             qDebug() << "OnReadDone ERR" ;
        }
    }
    void OnDone(const Status& s) override {
        std::unique_lock<std::mutex> l(mu_);
        status_ = s;
        done_ = true;
        cv_.notify_one();
    }
    Status Await() {
        std::unique_lock<std::mutex> l(mu_);
        cv_.wait(l, [this] { return done_; });
        return std::move(status_);
    }

private:
    ClientContext context_;
    float coord_factor_;
    Feature feature_;
    std::mutex mu_;
    std::condition_variable cv_;
    Status status_;
    bool done_ = false;
};

class Recorder : public grpc::ClientWriteReactor<Point> {
public:
    Recorder(RouteGuide::Stub* stub, float coord_factor,
             const std::vector<Feature>* feature_list)
        : coord_factor_(coord_factor),
        feature_list_(feature_list),
        generator_(
            std::chrono::system_clock::now().time_since_epoch().count()),
        feature_distribution_(0, feature_list->size() - 1),
        delay_distribution_(500, 1500) {
        stub->async()->RecordRoute(&context_, &stats_, this);
        // Use a hold since some StartWrites are invoked indirectly from a
        // delayed lambda in OnWriteDone rather than directly from the reaction
        // itself
        AddHold();
        //NextWrite();  //不在这人调用写也可以的
        StartCall();
    }
    void OnWriteDone(bool ok) override {
        // Delay and then do the next write or WritesDone

        qDebug() << "OnWriteDone" << QThread::currentThreadId() << ok;

        alarm_.Set(
            std::chrono::system_clock::now() +
                std::chrono::milliseconds(delay_distribution_(generator_)),
            [this](bool /*ok*/) { NextWrite(); });
    }
    void OnDone(const Status& s) override {
        std::unique_lock<std::mutex> l(mu_);
        status_ = s;
        done_ = true;
        cv_.notify_one();
    }
    Status Await(RouteSummary* stats) {
        std::unique_lock<std::mutex> l(mu_);
        cv_.wait(l, [this] { return done_; });
        *stats = stats_;
        return std::move(status_);
    }

public:
    void NextWrite() {
        if (points_remaining_ != 0) {
            const Feature& f =
                (*feature_list_)[feature_distribution_(generator_)];
            qDebug() << "Visiting point "
                     << f.location().latitude() / coord_factor_ << ", "
                     << f.location().longitude() / coord_factor_ ;
            StartWrite(&f.location());
            points_remaining_--;
        } else {
            StartWritesDone();
            RemoveHold();
        }
    }
    ClientContext context_;
    float coord_factor_;
    int points_remaining_ = 10;
    Point point_;
    RouteSummary stats_;
    const std::vector<Feature>* feature_list_;
    std::default_random_engine generator_;
    std::uniform_int_distribution<int> feature_distribution_;
    std::uniform_int_distribution<int> delay_distribution_;
    grpc::Alarm alarm_;
    std::mutex mu_;
    std::condition_variable cv_;
    Status status_;
    bool done_ = false;
};



extern std::shared_ptr<RouteGuideClient> g_client;

void RouteGuideClientTest();



#endif // ROUTEGUIDE_H
